#include"definicje.h"
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include"protokol.h"
#include"crc32.h"
#include<openssl/ssl.h>
#include<openssl/bio.h>
#include<openssl/err.h>

#define POWTARZAJ 20




/* Wysyła pakiet */
int wyslijpakietSSL(
    const int gniazdo,
    const struct sockaddr_storage * const adres,
    uint8_t * const d,
    SSL * ssl,
    BIO * bio,
      bool klient
    )
{
    SSL_set_bio(ssl,bio,bio);
    if (klient) SSL_set_connect_state(ssl);
    else SSL_set_accept_state(ssl);

   ssize_t wyslane;
   size_t iloscBajtow;
   struct naglowek *n=(struct naglowek *)d;
   iloscBajtow=rozmiarNaglowka+ntohs(n->rozmiar);
   obliczsume(d);
   //wyslane=sendto(gniazdo,d,iloscBajtow,0,(struct sockaddr *)adres,sizeof(struct sockaddr_storage));
#ifdef DEBUG
   fprintf(stderr,"SSL_write()\n");
#endif
   wyslane=SSL_write(ssl,d,iloscBajtow);
#ifdef DEBUG
   struct sockaddr_in *ain=(struct sockaddr_in *)adres;
   fprintf(stderr,"\nadres=%"PRIx32":%"PRIx16"\n",ntohl(ain->sin_addr.s_addr),ntohs(ain->sin_port));
#endif
   if(wyslane== -1)
   {
      perror("SSL_write");
      return -1;
   }
   if(wyslane < iloscBajtow)
   {
      fprintf(
	    stderr,
	    "Nie udało się wysłać pakietu o rozmiarze %zu\n",
	    iloscBajtow);
      return -1;
   }
   return 0;
}

/* Odbiera pakiet */
int odbierzpakietSSL(
    const int gniazdo,
    struct sockaddr_storage * const adres,
    uint8_t * const d,
    SSL * ssl,
    BIO * bio,
      bool klient
    )

{
    SSL_set_bio(ssl,bio,bio);
    if (klient) SSL_set_connect_state(ssl);
    else SSL_set_accept_state(ssl);

   ssize_t odebrane;
   socklen_t r=sizeof(struct sockaddr_storage);
   struct naglowek *n=(struct naglowek *)d;
   int16_t iloscBajtow;
#ifdef DEBUG
   fprintf(stderr,"funkcja odbierzpakiet()\n");
   fprintf(stderr,"ustawiam pamiec\n");
#endif
   memset(adres,0u,sizeof(struct sockaddr_storage));


#ifdef DEBUG
   fprintf(stderr,"SSL_read()\n");
#endif
   //odebrane=recvfrom(gniazdo,d,MAX_PAKIET,0,(struct sockaddr *)adres,&r);
   odebrane=SSL_read(ssl,d,MAX_PAKIET);
#ifdef DEBUG
   struct sockaddr_in *ain=(struct sockaddr_in *)adres;
#ifdef DEBUG
   fprintf(stderr,"wypiskaSSL\n");
#endif
   //fprintf(stderr,"\nadres=%"PRIx32":%"PRIx16"\n",ntohl(ain->sin_addr.s_addr),ntohs(ain->sin_port));
#endif
   if(odebrane==-1)
   {
      perror("SSL_read");
      return -1;
   }
   if(odebrane==0)
   {
      fprintf(
	    stderr,
	    "Serwer zamknął połączenie\n");
      return -1;
   }
   iloscBajtow=rozmiarNaglowka+ntohs(n->rozmiar);
   if(iloscBajtow != odebrane)
   {
      fprintf(
	    stderr,
	    "Ilosć bajtów odebranych nie zgadza się z rozmiarem "
	    "podanym w pakiecie\n");
      return -1;
   }
   if(iloscBajtow >= MAX_PAKIET)
   {
      fprintf(
	    stderr,
	    "Rozmiar podany w pakiecie jest nieprawidłowy\n");
      return -1;
   }
   if((n->operacja==OP_ACK) && (iloscBajtow < rozmiarACK))
   {
      fprintf(
	    stderr,
	    "Błędna ilość bajtów w pakiecie potwierdzającym\n");
      return -1;
   }
   if((n->operacja==OP_ERR) && (iloscBajtow < rozmiarACK))
   {
      fprintf(
	    stderr,
	    "Błędna ilość bajtów w pakiecie oznaczającym błąd\n");
      return -1;
   }
   if((n->operacja==OP_SET) && (iloscBajtow < rozmiarSET))
   {
      fprintf(
	    stderr,
	    "Błędna ilość bajtów w pakiecie ustawiającym opcje %zu\n",rozmiarSET);
      return -1;
   }
   if(!czyPoprawny(d))
   {
      fprintf(
	    stderr,
	    "Błędna suma kontrolna pakietu\n");
      return -1;
   }
   if(!poprawnaOperacja(n->operacja))
   {
      fprintf(
	    stderr,
	    "Niepoprawna operacja protokołu\n");
      return -1;
   }
   return 0;
}

/* Wysyła dopóki nie odbierze prawidłowego potwierdzenia */
int wyslijzpotwSSL(
    const int gniazdo,
    const struct sockaddr_storage * const adres,
    uint8_t * const d,
    uint8_t * const dp,
    uint32_t * const ktory,
    uint32_t * const ktoryTam,
    SSL * ssl,
    BIO * bio,
    bool klient
    )
{

   struct sockaddr_storage adres2;
   int wynik;
   unsigned int i=0;

   memset(&adres2,(int8_t)0u,sizeof(struct sockaddr_storage));
   struct naglowek *n=(struct naglowek *)d;
   struct pakietACK *p=(struct pakietACK *)dp;

   do
   {
      if(i>=POWTARZAJ)
      {
	 fprintf(stderr,"Przekroczono ilość błędnych pakietów\n");
	 return -1;
      }
      ++i;
      wynik=wyslijpakietSSL(gniazdo,adres,d,ssl,bio,klient);
      if(wynik==-1)
      {
	 return -1;
      }
      wynik=odbierzpakietSSL(gniazdo,&adres2,dp,ssl,bio,klient);
      if(wynik==-1)
      {
	 continue;
      }
#ifdef DEBUG
	 if(p->naglowek.operacja!=OP_ACK)
	    fprintf(stderr,"blad1\n");
	 if(p->naglowek.ktory!=htonl(*ktoryTam))
	    fprintf(stderr,"blad2\n");
	 if (p->ktory!=n->ktory)
	    fprintf(stderr,"blad3\n");
	 if(memcmp(adres,&adres2,sizeof(struct sockaddr_storage))!=0)
	    fprintf(stderr,"blad4\n");
#endif
   } while(
	 (p->naglowek.operacja!=OP_ACK) ||
	 (p->naglowek.ktory!=htonl(*ktoryTam)) ||
	 (p->ktory!=n->ktory) ||
	 (memcmp(adres,&adres2,sizeof(struct sockaddr_storage))!=0)
	 );
   ++(*ktory);
   ++(*ktoryTam);
   return 0;
}


/* Odbiera i wysyła potwierdzenie */
int odbierzzpotwSSL(
    const int gniazdo,
    struct sockaddr_storage * const adres,
    uint8_t * const d,
    uint32_t * const ktory,
    uint32_t * const ktoryTam,
    SSL * ssl,
    BIO * bio,
    bool klient
    )
{
   struct pakietACK p;
   int wynik;
   unsigned int i=0;
   struct naglowek *n=(struct naglowek *)d;

   p.naglowek.rozmiar=htons((uint16_t)4u);
   p.naglowek.opcje=(uint8_t)0u;
   p.naglowek.ktory=htonl(*ktory);
   p.ktory=htonl(*ktoryTam);

   do
   {
      if(i>=POWTARZAJ)
      {
	 fprintf(stderr,"Przekroczono ilość błędnych pakietów\n");
	 return -1;
      }
      ++i;
      wynik=odbierzpakietSSL(gniazdo,adres,d,ssl,bio,klient);
      if(wynik==-1 || (n->ktory!=htonl(*ktoryTam)))
      {
#ifdef DEBUG
	 fprintf(stderr,"xblad\n");
#endif
	 p.naglowek.operacja=OP_ERR;
	 wynik=wyslijpakietSSL(gniazdo,adres,(int8_t *)&p,ssl,bio,klient);
	 if(wynik==-1)
	 {
	    return -1;
	 }
	 continue;
      }
      else
      {
	 break;
      }

   } while(true);
   p.naglowek.operacja=OP_ACK;
   wynik=wyslijpakietSSL(gniazdo,adres,(int8_t *)&p,ssl,bio,klient);
   if(wynik==-1)
   {
	 return -1;
   }
   ++(*ktory);
   ++(*ktoryTam);
}

